package org.kerrlab.tools

object Utility {
  // These are WAY faster than relying upon object creation!
  def isNaN(f: Float) = java.lang.Float.isNaN(f)
  def isNaN(d: Double) = java.lang.Double.isNaN(d)
  
  // Simple math stuff
  def bound(s: Short, m: Short, M: Short): Short = math.min(M,math.max(m,s)).toShort
  def bound(i: Int, m: Int, M: Int): Int = math.min(M,math.max(m,i))
  def bound(l: Long, m: Long, M: Long): Long = math.min(M,math.max(m,l))
  def bound(f: Float, m: Float, M: Float): Float = math.min(M,math.max(m,f))
  def bound(d: Double, m: Double, M: Double): Double = math.min(M,math.max(m,d))
  def intervalSearch(a: Array[Int], i: Int) = {
    var l=0
    var r=a.length-1
    while (l+1<r) {
      val m = (l+r)/2
      if (a(m) <= i) l=m
      else r=m
    }
    l
  }
  def firstSearch[A](a: Seq[A])(i: Int, i0: Int = 0, i1: Int = a.length)(f: A => Int) = {
    var l = i0
    var r = i1-1
    while (l+1 < r) {
      val m = (l+r)/2
      if (f(a(m)) < i) l = m
      else r = m
    }
    if (f(a(l))==i) l else r
  }

  // Generic control structures
  def cfor[@specialized T](zero: T, okay: T=>Boolean, succ: T=>T)(act: T => Unit) {
    var i = zero
    while (okay(i)) {
      act(i)
      i = succ(i)
    }
  }
  def onlyIf[A](b: => Boolean)(f: => A) = if (b) Some(f) else None
  def lots[F](n: Int)(f: => F): F = if (n>1) { f; lots(n-1)(f) } else f

  // Timing
  def time[F](f: => F) = {
    var t0 = System.nanoTime
    val ans = f
    (ans , System.nanoTime - t0)
  }
  def ptime[F](f: => F) = {
    val ta = time(f)
    printf("Elapsed: %.3f s\n",1e-9*ta._2)
    ta._1
  }
  
  // Output utilities
  object Print {
    def me[A](a: A) = {
      a match {
        case aa: Array[_] => println(aa.deep)
        case _ => println(a)
      }
      a
    }
    
    def meWith[A,B](b: B)(a: A) = { me(b); me(a) }
    
    // Quick way to dump text to a file
    def toFile(f: java.io.File)(op: java.io.PrintWriter => Unit) {
      val p = new java.io.PrintWriter(f)
      try { op(p) } finally { p.close() }
    }
  }
  
  // Extra-simple counter which works like i++ in C
  class Incrementer(i0: Int) {
    def this() = this(0)
    protected var i = i0
    def ++ = { val j = i; i += 1; j }
  }

  // Generic mutable container
  class Changing[A](protected var changer: A) {
    def apply() = changer
    def update(a: A) { changer = a }
    def advance(f: A=>A) { changer = f(changer) }
    override def equals(a: Any) = changer.equals(a)
    override def hashCode = changer.hashCode
    override def toString = "~"+changer.toString+"~"
  }
  object Changing {
    def apply[A](a: A) = new Changing(a)
  }
  
  // These can be placed in case classes without changing equality, sort of like a comment
  class Caseless[A](private[Utility] val a: A) {
    override def hashCode = 0
    override def equals(o: Any) = o.isInstanceOf[Caseless[_]]
    override def toString = "/*"+a.asInstanceOf[AnyRef].getClass.getName+"*/"
  }
  implicit def anything_is_caseless[A](a: A) = new Caseless(a)
  implicit def caseless_is_anything[A](c: Caseless[A]) = c.a
  
  // Both mutable and invisible to case classes
  class Volatile[A](private[Utility] var a: A) {
    def apply() = a
    def update(aa: A) { a = aa }
    def advance(f: A=>A) { a = f(a) }
    override def hashCode = 0
    override def equals(o: Any) = o.isInstanceOf[Volatile[_]]
    override def toString = "/~"+a.asInstanceOf[AnyRef].getClass.getName+"~/"
  }
  object Volatile {
    def apply[A](a: A) = new Volatile(a)
  }
  implicit def volatile_is_anything[A](v: Volatile[A]) = v.a
  
  // Use this to create class-specific implicits that won't conflict with each other (A=implicit, B=class)
  class Implicated[A,B](private[Utility] val a: A, b: Class[B]) { }
  implicit def implicated_is_anything[A,B](i: Implicated[A,B]) = i.a
  
  // Generic methods that should be on every object
  class UtilityWrapper[A](a: A) {
    def use[Z](f: A=>Z) = f(a)
    def reuse[Z,Y](f: A=>Z)(g: (A,Z)=>Y) = g(a,f(a))
    def effect(f: A => Any) = { f(a); a }
    def effect(f1: A => Any, f2: A => Any, ff: (A => Any)*) = { f1(a); f2(a); ff.foreach(_(a)); a }
  }
  implicit def everything_has_utility[A](a: A) = new UtilityWrapper(a)
  
  // Methods for tuples
  class PairWrapper[A,B](ab: (A,B)) {
    def each[Y,Z](fl: A=>Y, fr: B=>Z) = (fl(ab._1),fr(ab._2))
    def fold[Z](f: (A,B)=>Z) = f(ab._1,ab._2)
  }
  implicit def pair_has_utility[A,B](ab: (A,B)) = new PairWrapper(ab)
  
  class PairOptionWrapper[A,B](ab: (Option[A],Option[B])) {
    def both[Z](f: (A,B)=>Z) = if (ab._1.isDefined && ab._2.isDefined) Some(f(ab._1.get,ab._2.get)) else None
  }
  implicit def pairoption_has_utility[A,B](ab: (Option[A],Option[B])) = new PairOptionWrapper[A,B](ab)
  
  class TrioWrapper[A,B,C](abc: (A,B,C)) {
    def each[X,Y,Z](fl: A=>X, fc: B=>Y, fr: C=>Z) = (fl(abc._1),fc(abc._2),fr(abc._3))
    def fold[Z](f: (A,B,C)=>Z) = f(abc._1,abc._2,abc._3)
  }
  implicit def trio_has_utility[A,B,C](abc: (A,B,C)) = new TrioWrapper(abc)

  // Methods for functions
  class DroppedFunction[-A,+B](f: A => Option[B]) extends PartialFunction[A,B] {
    private[this] var tested = false
    private[this] var arg: A = _
    private[this] var ans: Option[B] = None
    private[this] def cache(a: A) {
      if (!tested || a != arg) {
        tested = true
        arg = a
        ans = f(a)
      }
    }        
    def isDefinedAt(a: A) = {
      cache(a)
      ans.isDefined
    }
    def apply(a: A) = {
      cache(a)
      ans.get
    }
  }
  class DroppableFunction[A,B](f: A => Option[B]) {
    def drop = new DroppedFunction(f)
  }
  implicit def function_is_droppable[A,B](f: A => Option[B]) = new DroppableFunction(f)
        
  
  // Methods for collections
  class ExtendedIterable[A](it: Iterable[A]) {
    def headOr(a: A) = it.headOption.getOrElse(a)
    def pairfold[B,C](zero: B)(f: (A,A) => C)(g: (B,C) => B): B = {
      val i = it.iterator
      if (!i.hasNext) zero
      else {
        var b = zero
        var prev = i.next
        while (i.hasNext) {
          val a = i.next
          b = g(b, f(prev,a))
          prev = a
        }
        b
      }
    }
    def forallpair(f: (A,A) => Boolean) = {
      val i = it.iterator
      if (!i.hasNext) true
      else {
        var prev = i.next
        var ans = true
        while (ans && i.hasNext) {
          val x = i.next
          ans = f(prev,x)
          prev = x
        }
        ans
      }
    }
    def existspair(f: (A,A) => Boolean) = {
      val i = it.iterator
      if (!i.hasNext) false
      else {
        var prev = i.next
        var ans = false
        while (!ans && i.hasNext) {
          val x = i.next
          ans = f(prev,x)
          prev = x
        }
        ans
      }
    }
  }
  implicit def iterable_has_extension[A](it: Iterable[A]) = new ExtendedIterable(it)
  implicit def array_has_extension[A](a: Array[A]) = new ExtendedIterable(a: collection.mutable.WrappedArray[A])
  implicit def string_has_extension(s: String) = new ExtendedIterable(s: Seq[Char])
  
  import collection.generic.CanBuildFrom
  private[this] val vector_string_builder = new CanBuildFrom[String, String, Vector[String]] {
    def apply() = Vector.newBuilder[String]
    def apply(from: String) = this.apply()
  }
  class PimpedCollections[A, C, D[C]](ca: C)(implicit c2i: C => Iterable[A], cbf: CanBuildFrom[C,C,D[C]], cbfi: CanBuildFrom[C,A,C]) {
    def groupedWhile(p: (A,A) => Boolean): D[C] = {
      val it = c2i(ca).iterator
      val cca = cbf()
      if (!it.hasNext) cca.result
      else {
        val as = cbfi()
        var olda = it.next
        as += olda
        while (it.hasNext) {
          val a = it.next
          if (p(olda,a)) as += a
          else { cca += as.result; as.clear; as += a }
          olda = a
        }
        cca += as.result
      }
      cca.result
    }
    def groupedFold[B](b0: => B)(i: (B,A) => B)(p: (B,A) => Boolean): D[C] = {
      val it = c2i(ca).iterator
      val cca = cbf()
      if (!it.hasNext) cca.result
      else {
        val as = cbfi()
        var first = true
        val a0 = it.next
        as += a0
        var b = i(b0,a0)
        while (it.hasNext) {
          val a = it.next
          if (p(b,a)) { as += a; b = i(b,a) }
          else { cca += as.result; as.clear; as += a; b = i(b0,a) }
        }
        cca += as.result
      }
      cca.result
    }      
  }
  implicit def collections_have_pimps[A, C[A]](ca: C[A])(implicit c2i: C[A] => Iterable[A], cbf: CanBuildFrom[C[A],C[A],C[C[A]]], cbfi: CanBuildFrom[C[A],A,C[A]]) = 
    new PimpedCollections[A,C[A],C](ca)(c2i,cbf,cbfi)
  implicit def strings_have_pimps(s: String)(implicit c2i: String => Iterable[Char], cbfi: CanBuildFrom[String,Char,String]) =
    new PimpedCollections[Char,String,Vector](s)(c2i, vector_string_builder, cbfi)
   
  // Novel collections under development--will probably move to their own file or library at some point
  @deprecated("RingBuffer works, but only partially, and will be replaced as soon as possible.  Use at your own risk.","0.0")
  final class RingBuffer[A: ClassManifest](val maxsize: Int, initial: Option[Array[A]] = None)
    extends collection.mutable.Buffer[A]
    with collection.mutable.BufferLike[A, RingBuffer[A]]
    with collection.mutable.IndexedSeqOptimized[A, RingBuffer[A]]
  {
    private[this] val buffer = new Array[A](maxsize+1)
    private[this] var i0,i1 = 0
    private[this] def i0up = { i0 += 1; if (i0>=buffer.length) i0 -= buffer.length }
    private[this] def i0dn = { i0 -= 1; if (i0<0) i0 += buffer.length }
    private[this] def i1up = { i1 += 1; if (i1>=buffer.length) i1 -= buffer.length }
    private[this] def i1dn = { i1 -= 1; if (i1<0) i1 += buffer.length }   
    private[this] def me = this
    if (initial.isDefined) {
      val aa = initial.get
      if (aa.length <= maxsize) { System.arraycopy(aa,0,buffer,0,aa.length); i1 = aa.length }
      else { System.arraycopy(aa,aa.length-maxsize,buffer,0,maxsize); i1 = maxsize }
    }

    def this(a: Array[A]) = this(a.length, Some(a))
    override def newBuilder = RingBuffer.newBuilder(maxsize)
    def length = if (i1<i0) buffer.length+i1-i0 else i1-i0
    def apply(i: Int) = {
      val j = i+i0
      if (j >= buffer.length) buffer(j-buffer.length) else buffer(j)
    }
    def update(i: Int, elem: A) {
      val j = i+i0
      if (j >= buffer.length) buffer(j-buffer.length) = elem else buffer(j) = elem
    }
    def clear { i0 = 0; i1= 0 }
    def fresh = { clear; this }
    def +=(a: A) = {
      buffer(i1) = a
      i1up; if (i1==i0) i0up
      this
    }
    override def remove(n: Int, count: Int) = {
      if (n<length) {
        if (n+count >= length) i1 = (i0+n)%buffer.length
        else if (n<=0) i0 = (i0+count)%buffer.length
        else {
          val after = length - (n+count)
          if (after < n) {
            var i = (i0+n)%buffer.length
            var j = (i0+n+count)%buffer.length
            while (j != i1) {
              buffer(i) = buffer(j)
              i += 1
              if (i>=buffer.length) i = 0
              j += 1
              if (j>=buffer.length) j = 0
            }
            i1 = i
          }
          else {
            var i = (i0+n-1)%buffer.length
            var j = (i0+n+count-1)%buffer.length
            buffer(j) = buffer(i)
            while (i != i0) {
              i -= 1
              if (i<0) i = buffer.length - 1
              j -= 1
              if (j<0) j = buffer.length - 1
              buffer(j) = buffer(i)
            }
            i0 = j
          }
        }
      }
    }
    def remove(n: Int) = { if (n==0) poph else if (n+1==length) popt else { val ans = apply(n); remove(n,1); ans } }
    def insertAll(n: Int, elems: Traversable[A]) {
      if (n >= length) elems.foreach(this += _)
      else {
        val extra = new Array[A](length-n)
        i1 = (i0+n)%buffer.length
        var i = i1
        var j = 0
        while (j < extra.length) {
          extra(j) = buffer(i)
          j += 1
          i += 1
          if (i>=buffer.length) i = 0
        }
        elems.foreach(this += _)
        j = 0
        while (length < maxsize && j < extra.length) {
          this += extra(j)
          j += 1
        }
      }
    }
    def +=:(a: A) = {
      i0dn; if (i0==i1) i1dn
      buffer(i0) = a
      this
    }
    def :=+(a: A) = (this += a)
    def popt = {
      if (i1==i0) throw new java.util.NoSuchElementException
      i1dn; buffer(i1)
    }
    def poph = {
      if (i1==i0) throw new java.util.NoSuchElementException
      val ans = buffer(i0); i0up; ans
    }
  }
  object RingBuffer {
    def apply[A: ClassManifest](aa: A*) = { val rb = new RingBuffer[A](aa.length); aa.foreach(rb += _); rb }
    def empty[A: ClassManifest] = new RingBuffer[A](0)
    def newBuilder[A: ClassManifest] = (collection.mutable.ArrayBuilder.make[A]).mapResult(aa => new RingBuffer(aa))
    def newBuilder[A: ClassManifest](n: Int) = (collection.mutable.ArrayBuilder.make[A]).mapResult(aa => new RingBuffer(n,Some(aa)))
    implicit def canBuildFrom[A](implicit mf: ClassManifest[A]) = new CanBuildFrom[RingBuffer[_],A,RingBuffer[A]] {
      def apply() = newBuilder
      def apply(from: RingBuffer[_]) = newBuilder(from.maxsize)
    }
  }
  
  class SafeParsePrimitive(s: String) {
    private def nfe[T](t: => T) = try {Some(t)} catch {case nfe: NumberFormatException => None}
    def booleanOption = s.toLowerCase match {
      case "yes" | "true" => Some(true)
      case "no" | "false" => Some(false)
      case _ => None
    }
    def byteOption = nfe(s.toByte)
    def doubleOption = nfe(s.toDouble)
    def floatOption = nfe(s.toFloat)
    def hexOption = nfe(java.lang.Integer.valueOf(s,16))
    def hexLongOption = nfe(java.lang.Long.valueOf(s,16))
    def intOption = nfe(s.toInt)
    def longOption = nfe(s.toLong)
    def shortOption = nfe(s.toShort)
  }
  implicit def string_has_utility(s: String) = new SafeParsePrimitive(s)
  
  // XML handling with more data-type awareness
  class SmartXMLTag(n: scala.xml.Node) {
    def #@(s: String) = (n \ ("@"+s)).flatMap(_.text.intOption)
    def ##@(s: String) = (n \ ("@"+s)).flatMap(_.text.longOption)
    def ^@(s: String) = (n \ ("@"+s)).flatMap(_.text.doubleOption)
    def ~@(s: String) = (n \ ("@"+s)).map(_.text)
  }
  implicit def xmlnode_is_smarter(n: scala.xml.Node) = new SmartXMLTag(n)
  class SmartXMLTags(ns: scala.xml.NodeSeq) {
    def #@(s: String) = ns.flatMap(_ #@ s)
    def ##@(s: String) = ns.flatMap(_ ##@ s)
    def ^@(s: String) = ns.flatMap(_ ^@ s)
    def ~@(s: String) = ns.map(_ ~@ s)
  }
  implicit def xmlnodeseq_is_smarter(n: scala.xml.NodeSeq) = new SmartXMLTags(n)
  
  // Directory tools
  trait FileBackedStream extends java.io.InputStream {
    def size: Long
    def path: String
    def length = size
    def name = path.split(java.io.File.separatorChar).last
    def underlying: java.io.InputStream = this
  }
  def numberedFilePattern(s: String) = {
    """(.*?)(\d+)(\D*)""".r.unapplySeq(s).flatMap(l => {
      if (l.length==3) Some((l.head,l.tail.head,l.tail.tail.head)) else None
    }).flatMap(pnp => pnp._2.intOption.map(n => (pnp._1,n,pnp._3)))
  }
  class UtilitizedFile(file: java.io.File) {
    def listSequence(prepost: Option[Changing[(String,String)]] = None) = {
      val absone = file.getAbsoluteFile
      val onegroup = numberedFilePattern(absone.getName)
      onegroup.map(o => {
        prepost.foreach(_() = (o._1,o._3))
        val files = absone.getParentFile.listFiles      
        files.flatMap(
          f => numberedFilePattern(f.getName).filter(x => x._1==o._1 && x._3==o._3).map(x=>(x,f))
        ).sortBy(_._1._2).map(_._2)
      }).getOrElse(Array[java.io.File]())
    }
    def listRecursive(p: java.io.File => Boolean = (pf) => true): Array[java.io.File] = {
      if (!file.isDirectory) Array[java.io.File]()
      else {
        val files = file.listFiles
        val dirs = files.filter(_.isDirectory)
        files.filter(p) ++ dirs.flatMap(f => (new UtilitizedFile(f)).listRecursive(p))
      }
    }
    def recursiveStreams(p: String => Boolean = (pf) => true): Iterator[FileBackedStream] = {
      import scala.collection.JavaConversions._
      if (file.isDirectory) new Iterator[FileBackedStream] {
        val files = file.listFiles
        val selected = files.filter(f => !f.isDirectory && !f.getName.endsWith(".zip") && p(f.getAbsolutePath))
        val dirzipit = files.filter(f => f.isDirectory || f.getName.endsWith(".zip")).iterator.map(x => (new UtilitizedFile(x)).recursiveStreams(p)).flatten
        var idx = -1
        def hasNext = (idx+1 < selected.length) || dirzipit.hasNext
        def next = {
          if (idx+1 >= selected.length) dirzipit.next
          else {
            idx += 1;
            new java.io.FileInputStream(selected(idx)) with FileBackedStream {
              val path = selected(idx).getAbsolutePath
              val size = selected(idx).length
            }
          }
        }
      }
      else if (file.getName.endsWith(".zip")) {
        try {
          var opencount = 0
          var alldone = false
          val zf = new java.util.zip.ZipFile(file)
          val ent = zf.entries.filter(ze => !ze.isDirectory && p(file.getAbsolutePath + java.io.File.separator + ze.getName))
          new Iterator[FileBackedStream] {
            def hasNext = {
              val ans = ent.hasNext
              if (!ans && opencount==0) zf.close
              else alldone = !ans
              ans
            }
            def next = {
              val e = ent.next
              opencount += 1
              new java.io.FilterInputStream(zf.getInputStream(e)) with FileBackedStream {
                val size = e.getSize
                val path = file.getAbsolutePath + java.io.File.separator + e.getName
                override def underlying = in
                override def close() = {
                  super.close()
                  opencount -= 1
                  if (opencount==0 && alldone) zf.close
                }
              }
            }
          }
        }
        catch {
          case ze: java.util.zip.ZipException => List[FileBackedStream]().iterator
        }
      }
      else List[FileBackedStream]().iterator
    }
    def repath(prefixes: Array[String], newpre: String): Option[java.io.File] = {
      val g = file.getAbsolutePath
      if (g.length<2) None
      else if (prefixes.exists(_ == g)) Some(new java.io.File(newpre))
      else {
        val p = file.getAbsoluteFile.getParentFile
        val n = file.getName
        (new UtilitizedFile(p)).repath(prefixes,newpre).map(s => (new java.io.File(s,n)))
      }
    }
    def ensurePath() {
      val g = file.getAbsoluteFile.getParentFile
      if (!g.exists) {
        if (g.getPath.length > 1) (new UtilitizedFile(g)).ensurePath()
        g.mkdir()
      }
    }
    def retirePath() {
      val g = file.getAbsoluteFile.getParentFile
      if (!g.exists) (new UtilitizedFile(g)).retirePath()
      else if (g.listFiles.length == 0) {
        g.delete()
        (new UtilitizedFile(g)).retirePath()
      }
    }
    def hasDirectory(s: String): Boolean = {
      val g = file.getAbsoluteFile.getParentFile
      if (!g.getPath.contains(s)) false
      else if (g.getPath.length <= 1) false
      else if (g.getName == s) true
      else (new UtilitizedFile(g)).hasDirectory(s)
    }
  }
  implicit def file_has_utility(file: java.io.File) = new UtilitizedFile(file)

  // Methods that should be in nio.ByteBuffer but mysteriously aren't
  class RichByteBuffer(b: java.nio.ByteBuffer) {
    def apply() = b
    def getBytes(n: Int) = {
      val a = new Array[Byte](n); b.get(a); a
    }
    def getShorts(n: Int) = {
      val a = new Array[Short](n); var i=0; while (i<n) { a(i)=b.getShort(); i+=1 } ; a
    }
    def getInts(n: Int) = {
      val a = new Array[Int](n); var i=0; while (i<n) { a(i)=b.getInt(); i+=1 } ; a
    }
    def getFloats(n: Int) = {
      val a = new Array[Float](n); var i=0; while (i<n) { a(i)=b.getFloat(); i+=1 } ; a
    }
    def getDoubles(n: Int) = {
      val a = new Array[Double](n); var i=0; while (i<n) { a(i)=b.getDouble(); i+=1 } ; a
    }
  }
  implicit def bytebuffer2rich(b: java.nio.ByteBuffer) = new RichByteBuffer(b)
  implicit def rich2bytebuffer(rb: RichByteBuffer) = rb()
}
